-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local custom_msg = require 'messages.custom'
local context = require 'mc.context'
local log = require 'mc.logging'
local mdb_service = require 'mc.mdb.mdb_service'
local cjson = require 'cjson'
local m = {}

local PCIECARD_INTERFACE<const> = 'bmc.kepler.Systems.PCIeDevices.PCIeCard'
local DPUCARD_INTERFACE<const> = 'bmc.kepler.Systems.DPUCard'
local NETWORKADAPTER_INTERFACE<const> = 'bmc.kepler.Systems.NetworkAdapter'
local PCIE_ADDR_INFO_INTERFACE<const> = 'bmc.kepler.Systems.PcieAddrInfo'
local PCIECARD_COMPONENT_TYPE<const> = 8
local OCPCARD_COMPONENT_TYPE<const> = 83
local PCIEDEVICE_NETWORKADAPTER<const> = 2
local HOTPLUG_STATE_UNINSTALLED<const> = 4
local SYSTEMSTARTUPSTATE_BIOSPOSTCOMPLETE<const> = 254
local HOTPLUG_STATE_REMOVABLE<const> = 1
-- GetPath函数返回的无效路径
local INVALID_PATH<const> = ''

function m.get_members(slot, pcie_cards, ocp_cards)
    local uri_prefix = '/redfish/v1/Chassis/' .. slot .. '/PCIeDevices/'
    local members = {}

    if pcie_cards then
        for _, v in ipairs(pcie_cards) do
            local obj
            local ok, rsp = pcall(function()
                obj = mdb.get_object(bus, v, PCIECARD_INTERFACE)
            end)
            -- 对于有多个资源树路径的卡需要跳过查询失败的路径
            if not ok then
                log:debug('node id(%s) is invalid, err(%s)', v, rsp)
            else
                members[#members + 1] = {['@odata.id'] = uri_prefix .. (obj.NodeID or '')}
            end
        end
    end

    local ok, obj
    if ocp_cards then
        for _, v in ipairs(ocp_cards) do
            ok, obj = pcall(mdb.get_object, bus, v, PCIECARD_INTERFACE)
            if ok then
                members[#members + 1] = {['@odata.id'] = uri_prefix .. (obj.NodeID or '')}
            end
        end
    end

    return members
end

function m.get_count(pcie_cards, ocp_cards)
    local count = 0
    if pcie_cards then
        count = count + #pcie_cards
    end

    if ocp_cards then
        count = count + #ocp_cards
    end

    return count
end

function m.get_ExtendCardInfo(NetworkAdapters, SlotID, pf_mac_info)
    if not NetworkAdapters or not SlotID or not pf_mac_info then
        return {}
    end
    local list = {}

    for _, path in ipairs(NetworkAdapters) do
        local obj = mdb.get_object(bus, path, 'bmc.kepler.Systems.NetworkAdapter')
        if obj.ParentCardSlotId == SlotID then
            local PfMacInfoArr = {}
            for index, item in ipairs(pf_mac_info) do
                PfMacInfoArr[index] = {
                    ["Port"] = item[1] + 1,
                    ["PfId"] = item[2],
                    ["PermanentMac"] = item[3]
                }
            end
            list[#list + 1] = {
                ["ProductName"] = obj.Name,
                ["BoardId"] = obj.BoardIDHex,
                ["BoardName"] = obj.Name,
                ["Slot"] = obj.SlotNumber,
                ["PfMacInfo"] = PfMacInfoArr,
                ["Model"] = obj.Model,
                ["PCBVersion"] = obj.PCBVersion,
                ["DeviceId"] = obj.DeviceID ~= '0xFFFF' and obj.DeviceID or cjson.null,
                ["VendorId"] = obj.VendorID ~= '0xFFFF' and obj.VendorID or cjson.null,
                ["SubsystemId"] = obj.SubsystemDeviceID ~= '0xFFFF' and obj.SubsystemDeviceID or cjson.null,
                ["SubsystemVendorId"] = obj.SubsystemVendorID ~= '0xFFFF' and obj.SubsystemVendorID or cjson.null,
                ["Manufacturer"] = obj.Manufacturer,
                ["ChipManufacturer"] = obj.ChipManufacturer,
                ["Description"] = obj.ModelDescription,
            }
            break
        end
    end
    return list
end

local function get_table_len(tbl)
    if type(tbl) ~= 'table' then
        return
    end
    local count = 0
    for _, _ in pairs(tbl) do
        count = count + 1
    end
    return count
end

function m.is_dpu_card(node_id)
    -- node_id为空时应该返回404，而不应该返回400
    if not node_id then
        log:error('node_id is nil')
        return false
    end

    local filter = {NodeID = node_id}
    local ok, rsp = pcall(mdb_service.get_path, bus, PCIECARD_INTERFACE, cjson.encode(filter), true)
    -- 不是PCIe卡代表资源路径错误，返回404，不应该返回400
    if not ok then
        log:error('node id(%s) is invalid, err(%s)', node_id, rsp.message)
        return false
    end

    if rsp.Path == INVALID_PATH then
        log:error('Get invalid path')
        return false
    end

    -- 如果是PCIe卡，但是不是DPU卡，则返回400，表示不支持这个操作
    ok, rsp = pcall(mdb_service.get_object, bus, rsp.Path, {DPUCARD_INTERFACE})
    if not ok then
        log:error('Get DPUCard object failed')
        error(custom_msg.OperationFailed())
    end
    local count = get_table_len(rsp.Object)
    if not count or count == 0 then
        log:error('%s is not dpucard', node_id)
        error(custom_msg.OperationFailed())
    end
    return true
end

function m.get_device_class(function_class)
    if function_class == 0 then
        return "Other"
    elseif function_class == 1 then
        return "MassStorageController"
    elseif function_class == 2 then
        return "NetworkController"
    elseif function_class == 3 then
        return "DisplayController"
    elseif function_class == 4 then
        return "UnclassifiedDevice"
    elseif function_class == 5 then
        return "IntelligentController"
    elseif function_class == 6 then
        return "ProcessingAccelerators"
    elseif function_class == 7 then
        return "Other"
    elseif function_class == 8 then
        return "ProcessingAccelerators"
    elseif function_class == 9 then
        return "ProcessingAccelerators"
    elseif function_class == 10 then
        return "IntelligentController"
    elseif function_class == 11 then
        return "IntelligentController"
    else
        return "Other"
    end
end

local function get_valid_slot(obj)
    if not obj or not obj.SlotID or obj.SlotID == 0 or obj.SlotID == 255 then
        return cjson.null
    end
    return obj.SlotID
end

local function get_valid_slot_label(obj)
    local slot = get_valid_slot(obj)
    if slot == cjson.null then
        return cjson.null
    end
    return "Slot " .. slot
end

local function get_slot_links(chassis_id, pcie_addr_obj, pcie_cards, ocp_cards)
    local uri_prefix = '/redfish/v1/Chassis/' .. chassis_id .. '/PCIeDevices/'
    local members = {}, obj, ok
    if pcie_addr_obj.ComponentType == PCIECARD_COMPONENT_TYPE then
        if pcie_cards then
            for _, v in ipairs(pcie_cards) do
                ok, obj = pcall(mdb.get_object, bus, v, PCIECARD_INTERFACE)
                if ok and pcie_addr_obj.SlotID == obj.SlotID then
                    members[#members + 1] = {['@odata.id'] = uri_prefix .. (obj.NodeID or '')}
                    return members
                end
            end
        end
    end

    if pcie_addr_obj.ComponentType == OCPCARD_COMPONENT_TYPE then
        if ocp_cards then
            for _, v in ipairs(ocp_cards) do
                ok, obj = pcall(mdb.get_object, bus, v, PCIECARD_INTERFACE)
                if ok and pcie_addr_obj.SlotID == obj.SlotID then
                    members[#members + 1] = {['@odata.id'] = uri_prefix .. (obj.NodeID or '')}
                    return members
                end
            end
        end
    end

    return nil
end

function m.get_slot_count(pcie_addr_infos)
    local count = 0
    if pcie_addr_infos then
        local ok, obj
        for _, v in ipairs(pcie_addr_infos) do
            ok, obj = pcall(mdb.get_object, bus, v, PCIE_ADDR_INFO_INTERFACE)
            if ok and (obj.ComponentType == PCIECARD_COMPONENT_TYPE or obj.ComponentType == OCPCARD_COMPONENT_TYPE) then
                count = count + 1
            end
        end
    end
    return count
end

function m.get_slot_members(chassis_id, pcie_addr_infos, pcie_cards, ocp_cards)
    local members = {}
    if pcie_addr_infos then
        local ok, obj, obj_tbl, pcie_links
        for _, v in ipairs(pcie_addr_infos) do
            ok, obj = pcall(mdb.get_object, bus, v, PCIE_ADDR_INFO_INTERFACE)
            if ok and (obj.ComponentType == PCIECARD_COMPONENT_TYPE or obj.ComponentType == OCPCARD_COMPONENT_TYPE) then
                obj_tbl = {
                    ['PCIeType'] = obj.PCIeType,
                    ['Lanes'] = obj.Lanes,
                    ['SlotType'] = obj.SlotType,
                    ['Location'] = {
                        ['PartLocation'] = {
                            ['ServiceLabel'] = get_valid_slot_label(obj),
                            ['LocationOrdinalValue'] = get_valid_slot(obj),
                            ['LocationType'] = "Slot"
                        }
                    }
                }
                ok, pcie_links = pcall(function()
                    return get_slot_links(chassis_id, obj, pcie_cards, ocp_cards)
                end)
                if ok and pcie_links then
                    obj_tbl['Links'] = {
                        ['PCIeDevice'] = pcie_links
                    }
                end
                members[#members + 1] = obj_tbl
            end
        end
    end
    return members
end

function m.check_start_removing_device(DeviceName, FunctionClass, TargetReadyToRemove)
    local networkadapter_path = bus:call('bmc.kepler.maca', '/bmc/kepler/MdbService', 'bmc.kepler.Mdb', 'GetPath', 
    'a{ss}ssb', context.new(), "bmc.kepler.Systems.NetworkAdapter", 
    cjson.encode({DeviceLocator = DeviceName}), false)
    if networkadapter_path == '' then
        error(custom_messages.PropertyModificationNotSupported('ReadyToRemove'))
    end
    local obj = mdb.get_object(bus, networkadapter_path, NETWORKADAPTER_INTERFACE)
    
    local CurrentReadyToRemove = false
    if FunctionClass ~= PCIEDEVICE_NETWORKADAPTER then
        error(custom_messages.PropertyModificationNotSupported('ReadyToRemove'))
    elseif not obj.HotPluggable then
        error(custom_messages.PropertyModificationNotSupported('ReadyToRemove'))
    elseif obj.AttentionHotPlugState == HOTPLUG_STATE_UNINSTALLED then
        CurrentReadyToRemove = true
    end

    if CurrentReadyToRemove and not TargetReadyToRemove then
        error(custom_messages.PropertyModificationNotSupported('ReadyToRemove'))
    elseif not CurrentReadyToRemove and TargetReadyToRemove then
        local bios = mdb.get_object(bus, '/bmc/kepler/Systems/' .. obj.SystemID .. '/Bios', 'bmc.kepler.Systems.Bios')
        if bios.SystemStartupState ~= SYSTEMSTARTUPSTATE_BIOSPOSTCOMPLETE or obj.AttentionHotPlugState ~= HOTPLUG_STATE_REMOVABLE then
            error(custom_messages.PropertyModificationNotSupported('ReadyToRemove'))
        end
        return true
    else
        return false
    end
end

return m
